【DeFi的基础】IC容器内的ICP如何运作?
文章来自于|Yokayoka
投稿、转载请联系|DfinitySZ小助手
2021年12月21日,DFINITY 技术工程师 Roman Kashitsyn 就容器上的 ICP 的技术更新组织了社区对话。在这次社区对话中,Roman 谈到了团队最近添加到分类账 (ledger) 容器中的功能,其中包括取消了禁用一些容器转账 ICP 的限制。此外还增加了一个界面,允许代币持有者使用分类账 (ledger) 容器来转移资金。
视频资源:
https://www.youtube.com/watch?v=Hm-NWwiUQZw&t=1906s
1. ICP 的分类账(Ledger Model)
1.1 分类账: 账户标识符(account identifiers)
引入的第一个模型是账户标识符。账户标识符是一个 32 字节的字符串,用于唯一标识分类账中的账户。在互联网计算机上有一个主体 (principal) 的概念,它是用户或容器的标识符,是一个中间有破折号的 64 编码字符串。对于用户来说,基本上是他们的私钥或公钥的哈希。
在分类账 ledger 中,principal 就像一个持有资金的实体,分类账允许相同的主体有高达 264 数量的子账户。分类账通过一些计算将主体 principal 和子账户粘合到单个标识符下:计算主体和子账户的 SHA-224 哈希,以获得 28 字节哈希值,计算其 CRC32 哈希,并将其前置到字符串,从而获得完整的账户标识符。
因此,如果您得到 32 个字节的随机数,您可以将最后 28 个字节 CRC32 哈希,然后检查它们是否与前缀匹配。通过这种方式,分类账可以帮助避免意外转移到无法再移动的随机账户,并丢失这些 ICP。这就像信用卡中的校验和机制。
1.2 分类账: 区块(blocks)
第二个方面是区块链,就像我们在 NNS 子网上有互联网计算机区块链一样,但该区块链的区块至少目前不公开可见,除非有能够访问副本的存储,否则我们无法看到有人在那里做了什么。而分类账拥有自己的内部区块链,这比互联网计算机区块链或比特币区块链简单得多,因为一个区块只包含一个交易,如交易数据、时间戳和指向父区块的指针,而其他部分由互联网计算机完成。
让我们看看分类账是如何执行转账的:
分类账得到的是一个转账请求,其中包含想要转账的人的子账户,即调用者。
分类账将获取调用者和 from_subaccount,使用上述方案计算账户标识符,然后计算余额,以查看是否有足够的资金来执行此操作。
检查完成后,它会在链上创建一个块,忽略调用的用户。该块仅包含源和目标的账户标识符,其余字段仅从请求中复制。
2. 分类账 Candid 接口
2.1 分类账接口: 转账(transfer)
transfer : (record {.
#转账意味着你在分类账上将资金从一个账户转移到另一个账户,并支付一些费用
memo: nat64;
#分类账不关心此字段,我们可以将其设置为任何数字
amount: record { e8s : nat64 };
#金额是您希望从您的账户转移到其他人账户的金额
fee: record { e8s : nat64};
#费用是您愿意为交易支付的 ICP 金额;目前,它应始终为 1/10000 ICP
from_subaccount: opt blob;
#要用于计算账户标识符的子账户 32 字节字符串
to: AccountIdentifier;
#to 是资金在链上的去向,分类账将通过上述校验和对其进行检查,以确保在使用错误账户进行交易时不会浪费 ICP
created_at_time: opt record { timestamp_nanos : nat64 };
#当客户端构造事务时,输入就像时间戳一样被创建。这一点很重要,因为分类账确保在 24 小时窗口内没有重复的交易,并考虑到需要提前构建交易(如时间缓冲区)的交易所。
}) -> (variant { Ok: nat64; Err : Transfer Error });
#执行传输时得到的输出如果是正常的,就出现绝对块索引,如果出了问题,就返回 TransferError
type TransferError = variant {.
#TransferError 是一个变量,我们将所有的错误案例都放在这个错误的强类型元素中,可以编译出可能出现错误的部分,并相应地采取行动
BadFee : record { expected_fee : { e8s : nat64 } };
#其中一个错误是,您指定的费用不正确,比如费用太低,因此分类账会告诉您指定的费用不好,并会给您提供建议的费用
InsufficientFunds : record { balance : { e8s: nat64 } };
#此错误是您指定了错误的账户,或者此账户中没有足够的资金,然后分类账将告诉您没有足够的资金,以及您现在的余额
TxTooOld : record { allowed_window_nanos : nat64 };
#您可能会遇到的另一个问题是,如果您创建的时间太长(如超过 24 小时),则分类账无法有效检查此交易,因此它将拒绝此交易,如果您要应用它,则需要更改时间并再次尝试
TxCreatedInFuture : null;
#如果时间过于遥远,分类账也会拒绝它。因此,时间必须始终在限定范围内。
TxDuplicate : record { duplicate_of : BlockIndex; }};
#您可能遇到的最后一个错误是,如果此交易之前已被分类账看到,您将得到一个重复的回复,该回复表示存在已执行此交易的块。它的好处是,如果您试图在账本上运行某个东西,而您失去了电源或出现了故障,这种情况下,您可以再次应用完全相同的东西。如果它已经在分类账中,分类账将告诉您该交易已经存在的区块的索引。
2.2 分类账接口:账户余额(account_balance)
account_balance: ( record { account : AccountIdentifier } )-> (record { e8s: nat64}) query;
#这种方法非常直观。如果您想知道账户余额,只需指定您感兴趣的账户标识符,分类账就会返回该账户的余额
3. 支付模式
要实现支付,有两种方式:
一次性账户
相关标识符
现在让我们看看如何使用这两种方法通过分类账进行付款。
比如我们在商店里买东西,比如在咖啡店买一杯咖啡。问题是你如何组织你、商店和账本之间的通信,以便能够使用 ICP 中支付咖啡的费用。
当你买一杯咖啡时,商店通常做的是生成一张发票,例如,什么时间,什么人,从我这里买了一个什么咖啡,一共花了多少钱。所以,商店在互联网计算机上有一个容器,里面存放着发票,你的手机上有一个钱包应用程序,上面有 ICP。购买咖啡时,你想从咖啡店拿到发票,有多种方法可以实现,比如就像在咖啡店一样,向你展示一个二维码,你可以扫描和导入发票。
3.1 一次性账户(one-time account)
这里要提到的第一种付款方式是使用一次性账户。
钱包将发票中指定的金额转移到属于该商店的某个唯一账户中,例如,该账户可能被计算为该商店的负责人和发票的哈希,并将其用作子账户字符串,将它们合并在一起以获得账户标识符,并在分类账上向该唯一地址付款。因此,不同的发票将在分类账上生成不同的账户,并且每个发票都是唯一的。因此,每次付款都会在分类账中创建一个新的唯一账户。
分类账将向钱包回复已创建的付款区块和区块索引 N。钱包接下来要做的事情是返回到商店并告诉商店它已支付发票和付款区块。
下一步是商店想检查钱包是否真的支付了发票。下一步是商店想检查钱包是否真的支付了发票。这个方法被称为账户余额,即使用钱包用来支付的同一账户来做比较,因为它们使用相同的规则来比较账户标识符。在这种情况下,你可以使用发票的 SHA-256,因为商店是账户的所有者,因此没有其他人可以再将这些资金转移出去,商店能获得支付的确切金额。此时,商店可以检查发票金额是否与分类账上的金额相等,并确认发票已付款,并向钱包表示感谢。
下一个过程是一个商店,你想把这笔钱转移到你的主要子账户,这意味着你只需要创建另一个转账,一个新的分类账,金额等于发票减去你为转账支付的费用,然后,from_subaccount 再次使用发票和两个子账户的相同哈希,这两个子账户是分类账中的默认账户。
让我们总结一下一次性账户:
钱包不需要知道商店如何计算发票的唯一账户标识符。商店可以简单地将账户标识符包含到发票中。
您不必使用 SHA256,唯一的要求是每个交易的子账户必须是唯一的。使用顺序整数也同样有效。
缺少的接口: fetch _ blocks
下一种模式是,我们需要一个尚未创建好的接口,但我们正在努力将其引入到主网。
缺少的接口是 fetch_blocks,我们有 get_blocks,比如 protobuf,这很难使用,我们的目标是提供一些更易于容器构建的东西。这类似于我们将很快在分类账界面中看到的近似签名(approximate signature),称为 fetch_blocks。
fetch_blocks : (record {
from: nat64;
len: nat64;
}) -> (record {
blocks : vec Block;
chain_len : nat64;
});
#这个接口有两个参数,包括要提取的第一个块和要提取的块数,分类账将从一个数字返回一个连续的块数组,长度不超过指定的长度,以及总共有多少个块。
3.2 相关标识符(correlation identifiers)
第二个模式称为相关标识符。
通信的开始与商店生成发票时相同,钱包获得发票,但是其他部分略有不同。
钱包支付给商店的主账户,此时在这对中,发票需要包含一些唯一的 memo,即 64 位整数。转账将包括发票中的这个 memo,可能会使用默认子账户,但顾客也不需要知道。发票包含目标子账户。
下一步是,钱包将交易发送到分类账,然后分类账用块索引回复钱包,并在其到达商店的位置显示我已为发票付款,而这是块编号。
然后商店可以告诉 fetch_blocks 接口,该接口表示我有区块编号,可以从分类账中获取,并且只需要一个区块。如果区块包含具有指定 memo 的转账,并且转账的目的地账户属于商店,则它可以关闭发票并回复“一切正常”。
这种模式对各方都很友好,钱包支付的费用会更少,因为发票不包括商店从唯一的子账户转移资金所需支付的额外费用,而且对于商店来说,代码要简单得多,不需要管理多个子账户。而且分类账不需要为不同的人记录一堆特殊子账户,也不需要在上面浪费内存。
但关于相关标识符,我们仍有一些问题需要讨论:
两种模式中钱包<->商店地互动完全相同。因此,可以在不破坏客户机的情况下更改商店的实现。
使用相关标识符流更高效、更便宜:只需要一个块。
与一次性账户模式不同,相关标识符要求商店容器将发票保持在其状态。
尽管关联标识符比一次性账户便宜,但仍然存在一次性账户的应用场景,比如您不想在商店中保留任何状态。这就是最近添加的分类账容器的 Candid 接口的细节,并使更清楚地知道如何使用该接口来用 ICP 支付一杯卡布奇诺。
必看周刊
生态精选
寻宝回顾
精彩活动
联系我们
t.me/DfinitySZ
dfisz.com
twitter.com/DfinitySZ
twitter.com/DfinitySZCN
reddit.com/user/DfinityShenZhen